<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <style>

      .link {
        stroke: #000;
        stroke-width: 1.5px;
      }

      .node {
        cursor: move;
        fill: #ccc;
        stroke: #000;
        stroke-width: 1.5px;
      }

      .node.fixed {
        fill: #f00;
      }
      
      .node_label
      {
        font-family: Arial;
        font-size: 18px;
        font-weight: bold;
      }
      
      .link_label_src
      {
        font-family: Arial;
        font-size: 12px;
      }
      
      .link_label_trg
      {
        font-family: Arial;
        font-size: 12px;
      }

    </style>
    <script src="http://d3js.org/d3.v3.min.js"></script>
    </head>
  <body>
    <script>
      //define some test data
      var nodes = [
          {"x": 469, "y": 410, "label": "n1", "img" : "router.png", "img_width": 51, "img_height": 51},
          {"x": 493, "y": 364, "label": "n2", "img" : "switch.png", "img_width": 51, "img_height": 38},
          {"x": 442, "y": 365, "label": "n3", "img" : "host.png", "img_width": 51, "img_height": 69},
          {"x": 467, "y": 314, "label": "n4", "img" : "router.png", "img_width": 51, "img_height": 51},
          {"x": 477, "y": 248, "label": "n5", "img" : "host.png", "img_width": 51, "img_height": 69},
          {"x": 425, "y": 207, "label": "n6", "img" : "router.png", "img_width": 51, "img_height": 51},
          {"x": 402, "y": 155, "label": "n7", "img" : "switch.png", "img_width": 51, "img_height": 38},
          {"x": 369, "y": 196, "label": "n8", "img" : "router.png", "img_width": 51, "img_height": 51},
          {"x": 350, "y": 148, "label": "n9", "img" : "router.png", "img_width": 51, "img_height": 51},
          {"x": 539, "y": 222, "label": "n0", "img" : "switch.png", "img_width": 51, "img_height": 38},
          {"x": 594, "y": 235, "label": "n10", "img" : "switch.png", "img_width": 51, "img_height": 38},
          {"x": 582, "y": 185, "label": "n11", "img" : "host.png", "img_width": 51, "img_height": 69},
          {"x": 633, "y": 200, "label": "n12", "img" : "host.png", "img_width": 51, "img_height": 69}
        ];
      var links = [
          {"source":  0, "target":  1, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source":  1, "target":  2, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source":  2, "target":  0, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source":  1, "target":  3, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source":  3, "target":  2, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source":  3, "target":  4, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source":  4, "target":  5, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source":  5, "target":  6, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source":  5, "target":  7, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source":  6, "target":  7, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source":  6, "target":  8, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source":  7, "target":  8, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source":  9, "target":  4, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source":  9, "target": 11, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source":  9, "target": 10, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source": 10, "target": 11, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source": 11, "target": 12, "sourceif": "g0/1", "targetif": "g0/2"},
          {"source": 12, "target": 10, "sourceif": "g0/1", "targetif": "g0/2"}
        ];

      var width = 1240, height = 800;
      //create the force layout  
      var force = d3.layout.force()
          .size([width, height])
          .charge(-1500)
          .linkDistance(150)
          .on("tick", tick);

      //custom drag for moving labels separately to nodes
      var drag = d3.behavior.drag().on("dragstart", dragstart).on("drag", dragmove).on("dragend", dragend);
  
      //create the main svg
      var svg = d3.select("body").append("svg");

      var link = svg.selectAll(".link"),  //links lines
          node = svg.selectAll(".node"),  //nodes images
          labels = svg.selectAll(".node_label"),  //node labels
          srcLinkLabels = svg.selectAll(".link_label_src"),  //link src labels
          trgLinkLabels = svg.selectAll(".link_label_trg");  //link target labels

      //start of drag event
      function dragstart(d, i) {
          force.stop() // stops the force auto positioning before you start dragging
      }

      //drag movement
      function dragmove(d, i) {
        //change behaviour based on what type of object is dragging
        //note this will only ever be nodes or labels
        var obj = d3.select(this);
        if(obj.attr("class") == "node")
        {
          //simply move the node
          d.px += d3.event.dx;
          d.py += d3.event.dy;
          d.x += d3.event.dx;
          d.y += d3.event.dy; 
          tick(); // this is the key to make it work together with updating both px,py,x,y on d !
        }
        else
        {
          //moving a label
          //x,y are used effectively as an offset from the related node as the main positioning is
          //done via a transform based on the node position.  This way when a node is moved
          //the associated labels also move.
          obj.attr("x", parseFloat(obj.attr("x"))+d3.event.dx);
          obj.attr("y", parseFloat(obj.attr("y"))+d3.event.dy);
        }
      }

      //drop the drag
      function dragend(d, i) {
        d.fixed = true; // of course set the node to fixed so the force doesn't include the node in its auto positioning stuff
        tick();
        force.resume();
      }

      //provide the data to the force layout
      force.nodes(nodes).links(links).start();

      //create the link objects
      //the order of creation specifies the z display order
      link = link.data(links).enter().append("line").attr("class", "link");
            
      //nodes next
      node = node.data(nodes).enter().append("g")
          .attr("class", "node").call(drag);
      
      //add the image
      node.append("svg:image").attr("xlink:href", function(d,i){return d.img;})
        .attr("x",function(d,i){return -d.img_width/2;})
        .attr("y",function(d,i){return -d.img_height/2;})
        .attr("width", function(d,i){return d.img_width;})
        .attr("height", function(d,i){return d.img_height;});

      //followed by the labels so they display on top
      srcLinkLabels = srcLinkLabels.data(links).enter().append("text")
        .attr("class", "link_label_src").text(function(d, i) { return links[i].sourceif;})
        .attr("x",0).attr("y",0).call(drag);
      trgLinkLabels = trgLinkLabels.data(links).enter().append("text")
        .attr("class", "link_label_trg").text(function(d, i) { return links[i].targetif;})
        .attr("x",0).attr("y",0).call(drag);
      labels = labels.data(nodes).enter().append("svg:text").text(function(d, i) { return nodes[i].label;})
        .attr("x",0).attr("y",35).attr("class","node_label").call(drag);

      //force layout calculation step
      function tick() {
        //update all the svg element position based on the force layout positioning of nodes and links
        link.attr("transform", function(d) { return "translate(" + d.source.x + "," + d.source.y + ")"; });
        link.each(function(d, i) {
          this.setAttribute("x1",0);
          this.setAttribute("y1",0);
          this.setAttribute("x2",d.target.x-d.source.x);
          this.setAttribute("y2",d.target.y-d.source.y);
        });
        
        srcLinkLabels.each(function(d, i) {
          
          var xdiff = d.source.x-d.target.x;
          var ydiff = d.source.y-d.target.y;
          //was going to use slope to rotate the text inline with the link
          //however there is a bug in chrome that crashes the browser when 
          //this is done repeatedly so I have left it out at this stage
          var slope = (ydiff/xdiff)/Math.PI*180.0; 

          this.setAttribute("transform","translate(" + (d.source.x-xdiff*0.3) + "," + (d.source.y-ydiff*0.3) + ")");
        });
        
        trgLinkLabels.each(function(d, i) {
          
          var xdiff = d.source.x-d.target.x;
          var ydiff = d.source.y-d.target.y;
          var slope = (ydiff/xdiff)/Math.PI*180.0;

          this.setAttribute("transform","translate(" + (d.target.x+xdiff*0.3) + "," + (d.target.y+ydiff*0.3) + ")");
        });
        
        node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
        labels.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
      }

    </script>
  
  </body>
</html>